	LIST	NOCOND,NOGEN
;
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;
;	modifications by Mike Goetz and Hank Kee - 9/2/81
;
;	corrected PIP A:=B:filename.ext on Persci 277/299
;		Persci's must be configured as A: and B:
;		and/or C: and D: since they have but one
;		arm to access two drives
;	modified sign-on message to properly generate size
;		from MEMSIZE definition during assembly
;	corrected current drive assignment on warm boot
;	inclusion of LIST serial driver for CCS 8250 (30 CPS)
;		option to support LA34/LA36
;
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;
PERSCISW:	EQU	1		;ONE IF PERSCI DRIVE
LARGESW:	EQU	1 OR PERSCISW	;ONE IF MAXI DRIVE
NUMDRIVES:	EQU	2		;ONE TO FOUR (MUST BE SAME TYPE)
BIGIOSW:	EQU	1		;ZERO IF NO LIST, NO PUNCH
;
MEMSIZE:	EQU	64
SERIAL:		EQU	1		;SERIAL PRINTER OPTION W/8250
MEMSTAT:	EQU	MEMSIZE-9	;2400 HEX OFFSET
BEGINADR:	EQU	MEMSTAT*1024	;THIS IS THE START OF CPM
MEMUNITS:	EQU	MEMSIZE-(MEMSIZE/10*10)
MEMTENTH:	EQU	MEMSIZE/10
;
	ORG	1600H+BEGINADR 
;
;	CROMEMCO 4FDC I/O ASSIGNMENTS
;
CSTATPORT:	EQU	00H		;3P + S, 4FDC, TU-UART, OR SCC
CDATAPORT:	EQU	01H
IMODEPORT:	EQU	02H		;(3P + S DOESNT HAVE THESE)
IMASKPORT:	EQU	03H
PARLPORT:	EQU	04H		;NOT USED
;
STATPORT:	EQU	30H		;4FDC OR CCS 2422 BOARD
TRAKPORT:	EQU	31H
SECTPORT:	EQU	32H
DATAPORT:	EQU	33H
FLAGPORT:	EQU	34H
;
BANKPORT:	EQU	40H		;MEMORY BANKING PORT
;
;	1771/179X EQUATES
;
HEADLOAD:	EQU	08H
VERIFY:		EQU	04H
;
		IF	BIGIOSW = 1
;
;	OTHER DEFINITIONS (OPTIONAL)
;
BAUDRATE:	EQU	01H		;110 BAUD FOR READER ?
RSTATPORT:	EQU	10H
RBAUDPORT:	EQU	RSTATPORT	;TU-UART BOARD FOR PAPER TAPE
RDATAPORT:	EQU	RSTATPORT+1
PSTATPORT:	EQU	10H
PBAUDPORT:	EQU	PSTATPORT	;PAPER TAPE PUNCH ALSO
PDATAPORT:	EQU	PSTATPORT+1
LSTATPORT:	EQU	54H		;PARALLEL PRINTER POARD
LDATAPORT:	EQU	LSTATPORT
		ENDIF

TRUE:	EQU	-1
FALSE:	EQU	0
NULLS:	EQU	6		; NUMBER OF NULLS AFTER LF
LF:	EQU	0AH		; LINE FEED

SDATA:	EQU	20H		; DATA PORT
SINTEN:	EQU	21H		; INTERRUPT REGISTER
SIDENT:	EQU	22H		; INTERRUPT IDENTIFICATION
SLCTRL:	EQU	23H		; LINE CONTROL REGISTER
SMDMCT:	EQU	24H		; MODEM CONTROL REGISTER
SLSTAT:	EQU	25H		; LINE STATUS REGISTER
SMDMST:	EQU	26H		; MODEM STATUS REGISTER
SSTATP:	EQU	25H		; SERIAL PRINTER STATUS PORT (INPUT)
STBE:	EQU	20H		; SERIAL PRINTER TBE BIT


;
;	CPM ENTRY POINTS
CPMB:		EQU	00H+BEGINADR
BDOS:		EQU	806H+BEGINADR
;
;	JUMP VECTOR
	JP	CBOOT
EBOOT:
	JP	WBOOT
	JP	CONSTAT
	JP	CONIN
	JP	CONOUT
	JP	LIST
	JP	PUNCH
	JP	READER
	JP	HOME	
	JP	SELDSK		
	JP	SETTRK
	JP	SETSEC
	JP	SETDMA
	JP	READ
	JP	WRITE
	JP	LISTAT
	JP	SECTRAN
;
;	CP/M 2.2 DISK CONTROL BLOCKS
;
DPBASE:
	DW	XLT0,0000
	DW	0000,0000
	DW	DIRBUF,DPB0
	DW	CSV0,ALV0
;
	IF	NUMDRIVES > 1
	DW	XLT0,0000
	DW	0000,0000
	DW	DIRBUF,DPB0
	DW	CSV1,ALV1
	ENDIF
;
	IF	NUMDRIVES > 2
	DW	XLT0,0000
	DW	0000,0000
	DW	DIRBUF,DPB0
	DW	CSV2,ALV2
	ENDIF
;
	IF	NUMDRIVES > 3
	DW	XLT0,0000
	DW	0000,0000
	DW	DIRBUF,DPB0
	DW	CSV3,ALV3
	ENDIF
;
DPB0:
;
;	disk parameter block, common to all disks
;	this implements a "standard" CP/M directory
;	with 64 entries, and 1K blocks.
;
	DW	18+LARGESW*8	;sectors per track
	DB	3		;block shift factor
	DB	7		;block mask
	DB	0		;null mask
	DW	81+LARGESW*161	;disk size-1
	DW	63		;directory max
	DB	192		;alloc 0
	DB	0		;alloc 1
	DW	16		;check size
	DW	3-LARGESW	;track offset
;
XLT0:
;
;	sector translate vector
;
	IF	LARGESW EQ 0
	DB	1,6,11,16	;sectors 1,2,3,4
	DB	3,8,13,18	;sectors 5,6,7,8
	DB	5,10,15,2	;sectors 9,10,11,12
	DB	7,12,17,4	;sectors 13,14,15,16
	DB	9,14		;sectors 17,18
	ENDIF
;
;
	IF	LARGESW	EQ 1
	DB	1,7,13,19	;sectors 1,2,3,4
	DB	25,5,11,17	;sectors 5,6,7,8
	DB	23,3,9,15	;sectors 9,10,11,12
	DB	21,2,8,14	;sectors 13,14,15,16
	DB	20,26,6,12	;sectors 17,18,19,20
	DB	18,24,4,10	;sectors 21,22,23,24
	DB	16,22		;sectors 25,26
	ENDIF
;
;
SIGNON:
	DEFB	26,13,10,10
	DEFB	MEMTENTH+30H
	DEFB	MEMUNITS+30H
	DEFB	'k CP/M version 2.2'
	DEFB	13,10,0
;
SELDSK:	;
	LD	HL,0
	LD	A,C		;C CONTAINS REQUESTED DRIVE NO.
	CP	NUMDRIVES
	RET	NC		;IGNORE IF TOO HIGH
	LD	(DKNUMB),A
	LD	L,C	;L=disk number 0,1,2,3
	ADD	HL,HL	;*2
	ADD	HL,HL	;*4
	ADD	HL,HL	;*8
	ADD	HL,HL	;*16 (size of each header)
	LD	DE,DPBASE
	ADD	HL,DE	;HL=.dpbase(diskno*16)
	RET	
;
SECTRAN:	;TRANSLATE SECTOR IN C USING TABLE AT DE
	LD	B,0
	EX	DE,HL		;TABLE ADDR TO HL
	ADD	HL,BC		;GET ADDRESS
	LD	L,(HL)		;GET BYTE
	LD	H,0		;ANSWER IN HL
	RET
;
SETSEC:
	LD	A,C		;JUST SAVE SECTOR NUMBER
	LD	(DKSECT),A
	RET
;
SETDMA:
	LD	(DKDMA),BC	;JUST SAVE I/O ADDRESS
	RET
;
;
;	ERROR CHECKING READ AND WRITE RTNS FOR
;	CROMEMCO CBIOS
;
READ:
	CALL	CLEAR
RETRYREAD:
	CALL	READ4FDC	;READ SECTOR
	RET	Z		;SUCCESSFUL READ. RETURN
	CALL	ERROR		;INCREMENT RETRYCOUNT
	JR	NZ,RETRYREAD	;RETRY 20 TIMES
	OR	01H		;CP/M CONVENTION FOR PERMANENT ERROR
	RET	
;
CLEAR:
	XOR	A
	LD	(TRKERCNT),A	;ZERO OUT TRACK ERROR COUNTER
	LD	(RETRYCOUNT),A	;ZERO OUT CRC ERROR COUNTER
	RET	
;
WRITE:
	CALL	CLEAR
RETRYWRITE:
	CALL	WRIT4FDC	;WRITE SECTOR
	RET	Z		;SUCCESSFUL WRITE. RETURN
	CALL	ERROR		;INCREMENT RETRYCOUNT
	JR	NZ,RETRYWRITE	;RETRY 20 TIMES
	OR	01H		;CP/M CONVENTION FOR PERMANENT ERROR
	RET	
;
ERROR:
	PUSH	HL
	AND	10H		;CHECK FOR NRF
	JR	NZ,TRACKERROR	
	LD	HL,RETRYCOUNT
	INC	(HL)		;INCREMENT RETRYCOUNT
	LD	A,(HL)
	POP	HL
	SUB	20		;20 TRIES?
	RET	
;
TRACKERROR:
	LD	HL,TRKERCNT
	INC	(HL)		;INCREMENT NO OF TRACK ERRORS
	LD	A,(HL)
	SUB	10D		;ALLOW ONLY 10 TRACK ERRORS
	POP	HL
	RET	Z		;IF >10, RETURN A FAILURE
	PUSH	BC
	CALL	HOME		;HOME THE HEAD
	LD	A,(DKTRACK)
	LD	C,A		;GET TRACK IN C
	CALL	SETTRK		;RESEEK TO CORRECT TRACK
	POP	BC
	OR	0FFH		;RETRY
	RET	
;
;	RESTORE THE DISK TO TRACK ZERO
;
HOME:
	SUB	A,A		;ZERO OUT TRACK COUNTER
	LD	(DKTRACK),A
	CALL	DISKSELECT	;NOW SELECT THE DISK
	OUT	FLAGPORT,A
	LD	A,02H+HEADLOAD+VERIFY	;USE SLOW STEPPING SPEED FOR ALL DISKS
	OUT	STATPORT,A
RSTI:
	IN	A,FLAGPORT	;NOW CHECK STATUS
	RRA
	JR	NC,RSTI		;LOOP BACK UNTIL DONE
	JP	SEEKTEST
;
;	HERE, ACTUALLY DO THE READ OPERATION
;
READ4FDC:
	PUSH	BC
	PUSH	HL		;FIRST, SAVE REGS
	PUSH	DE
	LD	E,88H		;READ COMMAND (VALID FOR 1771, 179X)
	CALL	INIT4FDC
RDI1:
	IN	A,FLAGPORT	;NOW CHECK FLAGS
	RRA
	JR	C,RDI3		;IF PREMATURELY DONE, STOP
	INI
	JR	NZ,RDI1		;READ ANOTHER BYTE INTO CORE UNTIL DONE
RDI2:
	IN	A,FLAGPORT	;CHECK FLAGS
	RRA
	JR	NC,RDI2		;LOOP UNTIL READY
RDI3:
	IN	A,STATPORT	;NOW CHECK STATUS
	AND	A,9CH
RDWREND:
	EI
	POP	DE		;RESTORE REGS
	POP	HL
	POP	BC
	RET
;
;	ACTUALLY DO WRITE OPERATION
;
WRIT4FDC:
	PUSH	BC
	PUSH	HL		;SAVE REGS
	PUSH	DE
	LD	E,0A8H		;WRITE COMMAND (VALID FOR 1771, 179X)
	CALL	INIT4FDC
WRI1:
	IN	A,FLAGPORT	;CHECK FLAGS
	RRA
	JR	C,WRI3		;IF PREMATURELY DONE, STOP
	OUTI
	JR	NZ,WRI1		;WRITE DATA FROM MEMORY TIL DONE
WRI2:
	IN	A,FLAGPORT	;CHECK FLAGS
	RRA
	JR	NC,WRI2		;LOOP TIL DONE WITH WHOLE OPERATION
WRI3:
	IN	A,STATPORT	;NOW CHECK ERROR STATUS
	AND	A,0FCH
	JR	RDWREND
;
;	SET THE TRACK, AND MOVE DISK ARM THERE
;
SETTRK:
	LD	A,C
	LD	(DKTRACK),A	;STORE THE TRACK NUMBER
	SUB	A,A
	CALL	DISKSELECT	;NOW SELECT THE DISK
	OUT	FLAGPORT,A
	LD	A,C
	OUT	DATAPORT,A	;TELL 1771 ABOUT TRACK WANTED
	LD	A,(DKSECT)
	OUT	SECTPORT,A	;TELL IT ABOUT SECTOR WANTED
	PUSH	HL
	PUSH	DE
	LD	HL,LOGINTAB	;NOW, SEE WHERE DISK ARM IS NOW
	LD	A,(DKNUMB)
	LD	E,A		;LOOK UP IN TABLE
	LD	D,0
	ADD	HL,DE		;GET BYTE
	LD	A,(HL)
	OUT	TRAKPORT,A	;TELL 1771 WHERE ARM IS NOW
	LD	A,(HL)
	SUB	A,C
	POP	DE		;NOW CAN RESTORE THE REGS
	POP	HL
	RET	Z		;IF ALREADY AT THAT TRACK, QUIT
	LD	A,12H-LARGESW*2+HEADLOAD+VERIFY
	OUT	STATPORT,A	;PERFORM THE SEEK THAT IS NEEDED
SKI:
	IN	A,FLAGPORT	;CHECK FLAGS
	RRA
	JR	NC,SKI		;LOOP UNITL OPERATION DONE
SEEKTEST:
	IN	A,STATPORT	;NOW CHECK ERROR STATUS
	AND	A,98H
	RET	NZ		;ZERO IS ALL OK
	PUSH	DE
	LD	D,0		;UPDATE TRACK TABLE
	LD	A,(DKNUMB)
	LD	E,A		;WITH NEW POSITION
;
	IF	PERSCISW = 1
	CALL	ADDSHIFT	;PERSCI DRIVES HAVE ONLY ONE ARM
	LD	A,E
	XOR	1
	LD	E,A
	CALL	ADDSHIFT
	POP	DE
	RET
	ENDIF
;
ADDSHIFT:			;IF NOT PERSCI, FALL THRU HERE
	PUSH	HL
	LD	HL,LOGINTAB	;GET CORRECT ADDRESS OF DISK
	ADD	HL,DE
	LD	A,(DKTRACK)	;AND GET CURRENT TRACK NUMBER
	LD	(HL),A
	SUB	A,A		;PUT IT IN
	POP	HL
	IF	PERSCISW = 0
	POP	DE		;AND MISC CLEANUP
	ENDIF
	RET
;
;	INITIALIZE THE 4FDC, BY SELECTING THE DISK AND
;	TURNING ON THE DISK MOTORS, AND INITIALIZE THE
;	REGISTERS FOR THE I/O OPERATION
;
INIT4FDC:
	LD	A,80H		;FIRST, SELECT THE DISK
	CALL	DISKSELECT
	LD	HL,(DKDMA)	;INITIALIZE HL AND BC REGS FOR I/O
	LD	BC,8000H+DATAPORT	;80H IS 128 BYTE SECTORS
	LD	D,A		;SAVE THE A REG
	DI			;DISABLE INTERRUPTS
	LD	A,(DKSECT)
	OUT	SECTPORT,A	;SET THE SECTOR WANTED
	IN	A,FLAGPORT
	CPL			;SEE IF HEAD IS LOADED
	AND	A,20H
	JR	Z,IN0
	LD	A,04		;THIS IS THE HEAD LOAD BIT
IN0:
	ADD	A,E		;ADD HEAD LOAD BIT TO COMMAND
	LD	E,A
	LD	A,D		;RESTORE THE A REG
	OUT	FLAGPORT,A
	LD	A,E
	OUT	STATPORT,A	;OUTPUT THE COMMAND
	RET
;
;	FIGURE OUT WHICH DISK TO SELECT AND PUT BIT THERE
;
DISKSELECT:
	PUSH	BC		;FIRST, SAVE THIS, ITS NEEDED
	LD	C,A
	LD	A,(DKNUMB)	;GET DISK NUMBER 0-3
	LD	B,A
	INC	B		;ADD ONE
	SUB	A,A
	SCF			;SET A BIT IN TO BE SHIFTED
SHIFTBIT:
	RLA			;NOW, ROTATE BITS OVER
	DJNZ	SHIFTBIT
	LD	B,A		;SAVE THE NEW VALUE
	XOR	A
	OR	A,20H+LARGESW*10H ;CONDITION 4FDC FOR MOTOR ON AND MAXI
	OR	A,B
	OR	A,C		;OR IN DRIVE SELECT AND COMMAND WANTED
	POP	BC
	RET
;
;	GETS CONTROL ON A WARM START
;
WBOOT:
	LD	A,01H		;FIRST, RESTORE DEFAULT BANKING
	OUT	BANKPORT,A
;
	LD	SP,80H
	LD	A,(DKNUMB)
	LD	(CURRDRIVE),A	;STORE SELECTED DRIVE
;
STARTBOOT:
	LD	C,0
	CALL	SELDSK		;SELECT DRIVE A TO REBOOT
	CALL	HOME
	LD	HL,CPMB-128	;WILL INCREMENT BY 128 LATER
	LD	(DKDMA),HL
	LD	BC,44*256+1	;SECTOR COUNT, FIRST SECTOR-1
RDSEC:
	LD	A,C		;C IS SECTOR NUMBER
	CP	18+LARGESW*8
	JR	Z,NXTTRK
	LD	DE,128		;INCREMENT DMA ADDRESS
	LD	HL,(DKDMA)
	ADD	HL,DE
	LD	(DKDMA),HL	;STORE DMA ADDRESS
	INC	C		;INCREMENT SECTOR NUMBER
	CALL	SETSEC
	CALL	READ		;READ THE DATA
	JR	NZ,WBOOT	;ON READ FAILURE TRY AGAIN
	DJNZ	RDSEC
	JR	BOOT		;READ CCP AND BDOS;
NXTTRK:
	LD	A,(DKTRACK)
	CP	2-LARGESW
	JR	Z,BOOT		;STOP AT TRACK 2
	LD	C,A
	INC	C		;SEEK NEXT TRACK IF NOT
	CALL	SETTRK
	LD	C,0
	JR	RDSEC
;
;
CBOOT:	;AFTER COLD BOOT
	LD	SP,80H		;INITIALIZE STACK FOR ROUTINE
	LD	HL,SIGNON
	CALL	PRMSG		;PRINT MESSAGE
BOOT:	;GETS CONTROL AFTER COLD OR WARM BOOT
	DI	
	LD	A,0C3H		;SET UP PARAMETERS ON PAGE 0
	LD	(0),A
	LD	HL,EBOOT
	LD	(1),HL
	LD	(7*8+1),HL
	LD	(5),A
	LD	HL,BDOS
	LD	(6),HL
;
;		CROMEMCO INITIALIZATION HERE (READER, LIST NOT NEEDED)
;		NOTE .. THE CONSOLE TUART IS INITIALIZED BY RDOS
;
	IF	BIGIOSW = 1
	LD	A,BAUDRATE
	OUT	PBAUDPORT,A
	ENDIF
;
	IF	SERIAL = 1
	CALL	L2INIT
	ENDIF
;
	LD	BC,80H		;SET DEFAULT DMA ADDR
	CALL	SETDMA
;
	LD	A,(CURRDRIVE)	;RESELECT THE DRIVE THAT WAS ACTIVE
	LD	C,A		;  BEFORE THE WARM START
	EI	
	JP	CPMB
;
;
ERRMSG:
	DEFB	13,10,'?? ERROR ??',13,10,0
;
PRMSG:	;PRINT MESSAGE AT H,L UNTIL 0
	LD	A,(HL)
	OR	A		;ZERO?
	RET	Z
	LD	C,A		;GO PRINT CHAR
	CALL	CONOUT
	INC	HL		;GET NEXT CHAR
	JR	PRMSG
;
;
;HARDWARE UART CONSOLE ROUTINES
;
CONSTAT:
	IN	A,CSTATPORT	;CHECK CONSOLE STATUS
	AND	40H
	RET	Z		;ZERO MEANS NO INPUT BYTE READY
	LD	A,0FFH
	RET			;FF MEANS INPUT
;
CONIN:
	CALL	CONSTAT		;CHECK STATUS TIL GOT A BYTE
	JR	Z,CONIN
	IN	A,CDATAPORT	;READ THE BYTE AND KILL OFF 80H
	AND	7FH
;
;	THE FOLLOWING CODE REPLACES THE DEL KEY WITH A CTRL-U,
;	SO THAT DEL MEANS LINE DELETE. USE BACKSPACE TO DELETE
;	A SINGLE CHARACTER.
;
;	CP	127	; DEL
;	RET	NZ
;	SUB	127-21	; CTRL-U
	RET	
;
;	CONSOLE OUTPUT ROUTINE
;
CONOUT:
	IN	A,CSTATPORT	;FIRST, LOOP UNITL TRANSMITTER BUFFER
	AND	80H		;IS EMPTY
	JR	Z,CONOUT
	LD	A,C		;NOW, OUTPUT CHARACTER TO CONSOLE
	OUT	CDATAPORT,A
	RET	
;
;	LIST STATUS CHECK FOR CP/M 2.2
;
LISTAT:
	IF	BIGIOSW = 1
	IN	A,LSTATPORT	;IF LIST TRANSMITTER IS BUSY,
	CPL			;THEN RETURN WITH ZERO
	AND	20H
	RET	Z
	OR	A,0FFH		;LIST TRANSMITTER BUFFER IS EMPTY
	RET
	ENDIF
;
LIST:
	IF BIGIOSW = 1 AND SERIAL = 0
	CALL	LISTAT		;CHECK IF PRINTER BUSY
	JR	Z,LIST
	LD	A,C		;NOW OUTPUT CHARACTER
	SET	7,A
	OUT	LDATAPORT,A	;WITH HIGH STROBE
	RES	7,A
	OUT	LDATAPORT,A	;NOW LOW STROBE
	SET	7,A
	OUT	LDATAPORT,A	;NOW HIGH STROBE AGAIN
	RET
	ENDIF
;
	IF	SERIAL = 1
	LD	A,C
	JP	L2OUT
	ENDIF
;
PUNCH:
	IF	BIGIOSW = 1
	IN	A,PSTATPORT	;CHECK IF PUNCH BUFFER EMPTY
	AND	80H
	JR	Z,PUNCH		;LOOP UNTIL READY
	LD	A,C
	OUT	PDATAPORT,A	;OUTPUT CHARACTER
	RET
	ENDIF
;
READER:
	IF	BIGIOSW = 1
	IN	A,RSTATPORT	;SEE IF READER BUFFER FULL
	AND	40H
	JR	Z,READER	;LOOP UNTIL FULL
	IN	A,RDATAPORT	;AND READ IT
	RET	
	ENDIF
;
	IF	BIGIOSW NE 1
	XOR	A,A		;DUMMY ROUTINE FOR LIST,PUNCH,READER
	RET
	ENDIF


;  Serial Printer Initialization Routine

L2INIT:	LD	A,0FH		; SETUP THE 8250
	OUT	SMDMCT,A	
	LD	A,83H		; SET DIVISOR REGISTER ACCESS
	OUT	SLCTRL,A	
	LD	A,80H		; SET THE DIVISOR TO 384=300 BAUD
	OUT	SDATA,A
	LD	A,01H		;
	OUT	SINTEN,A	
	LD	A,03H		; SET DATA REGISTER ACCESS
	OUT	SLCTRL,A	
	LD	A,0H		; DISABLE INTERRUPT
	OUT	SINTEN,A	
	OUT	SLSTAT,A	; RESET ERROR FLAGS
	RET


;  Get Serial Printer Output Status
;  Upon Exit:	A = -1 (FFH) and Z-flag is reset if ready for char.
;		A = 0 and Z-flag is set if not ready for character

L2RDY:	IN	A,SSTATP	; GET LIST-OUT STATUS
	AND	STBE		; CHECK PRINTER TBE FLAG
	RET	Z		; PRINTER NOT READY FOR CHARACTER
	LD	A,-1		; PRINTER READY FOR CHARACTER
	RET


;  Serial Printer Output Routine
;  Upon Entry:	A contains the character to be output

L2OUT:	PUSH	AF		; SAVE CHARACTER FOR A MOMENT
L2OT30:	CALL	L2RDY		; GET LIST-OUT STATUS
	JR	Z,L2OT30	; ZERO MEANS PRINTER BUSY
	POP	AF		; RESTORE CHARACTER
	OUT	SDATA,A		; OUTPUT THE CHARACTER
	CP	LF		; CHECK FOR END OF LINE
	RET	NZ		; RETURN IF NOT LINE FEED CHARACTER
	LD	A,NULLS+1	; IF LF, GET NUMBER OF NULLS
L2RTN:	DEC	A		; CHECK FOR 0 NULLS AT TOP OF LOOP
	RET	Z		; RETURN IF ALL NULLS OUTPUT
	PUSH	AF		; SAVE NULLS COUNTER
	SUB	A		; PRINT A SINGLE NULL
	CALL	L2OUT		;	CHARACTER (RECURSIVE)
	POP	AF		; RESTORE NULLS COUNTER
	JR	L2RTN		; LOOP TO PRINT NEXT NULL
;
DKNUMB:	EQU	4		;THIS IS WHERE THE DISK NUMBER IS
;				;KEPT IN NORMAL CP/M
CURRDRIVE:
	DB	0		;THE SAVE AREA FOR THE DEFAULT DRIVE
LOGINTAB:
	DB	2-LARGESW,0,0,0		;FOUR DRIVES MAX
DKSECT:
	DB	0		;SECTOR NUMBER
DKTRACK:
	DB	0		;TRACK NUMBER
DKDMA:
	DW	0		;DMA ADDRESS
TRKERCNT:
	DB	0		;NUMBER OF TRACK ERRORS
RETRYCOUNT:
	DB	0		;NUMBER OF READ ERRORS
;
;	FROM HERE ON, THE AREAS ARE INITIALIZED BY CP/M AS NEEDED
;
DIRBUF:
	DS	128		;SAVE AREA FOR DISK DIRECTORY OPERATIONS
;
;
ALV0:
	DS	32
;
	IF	NUMDRIVES > 1
ALV1:
	DS	32
	ENDIF
;
	IF	NUMDRIVES > 2
ALV2:
	DS	32
	ENDIF
;
	IF	NUMDRIVES > 3
ALV3:
	DS	32
	ENDIF
;
CSV0:
	DS	16
;
	IF	NUMDRIVES > 1
CSV1:
	DS	16
	ENDIF
;
	IF	NUMDRIVES > 2
CSV2:
	DS	16
	ENDIF
;
	IF	NUMDRIVES > 3
CSV3:
	DS	16
	ENDIF
;

;  Note:  The last assembled byte of this module MUST NOT be a Define
;  Storage (DS or DEFS) pseudo-op to assure proper operation with CDOSGEN

	END
